home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Libraries / SAT 2.3a1 / OffscreenToys SAT / OffscreenToysSAT.p < prev    next >
Encoding:
Text File  |  1994-11-03  |  24.4 KB  |  810 lines  |  [TEXT/PJMM]

  1. {--------- OFFSCREEN TOYS with SAT ---------}
  2. {by Ingemar Ragnemalm 1994}
  3.  
  4. {Offscreen Toys is a nice little demo I made to make a SAT-like demo with complete source}
  5. {code, independent of any libraries (except Apple's). Now, this should be easier to do with}
  6. {SAT, right? Well, partially so, but while adapting it to SAT, I ran into a minor flaw that}
  7. {SAT had (and doesn't have from version 2.1 and up), namely that drawing took place after}
  8. {moving sprites, but before checking for collisions, which didn't look too good in programs}
  9. {where sprites bounce off each other. After fixing this flaw, I would say that the SAT}
  10. {version is indeed a bit better than the independent version. The result is quite a bit faster}
  11. {and with asynch sound.}
  12. {}
  13. {As yet another SAT demo, what does it give us?}
  14. {• If you wonder what it costs to have a real event loop, or good collision handling, this demo}
  15. {shows that pretty well.}
  16. {• Demonstrates a moveable window AND fast mode at the same time, with the precautions}
  17. {that demands when moving the window. (The window mustn't be moved outside the screen,}
  18. {some internal SAT variables – that you otherwise should never care about – must be adjusted,}
  19. {and we must stay word-aligned to make it work in b/w).}
  20. {}
  21. {I don't consider this demo final in any way. Known flaws:}
  22. {• I have made some mistakes in the port-setting. Nothing fatal, I think. FIXED.}
  23. {• I don't protect the user from moving the window outside the screen, which might be fatal}
  24. {when the SAT blitters are turned on. FIXED.}
  25. {• Like in the "real" Offscreen Toys, there is a bug that causes sprites to disappear for a short}
  26. {while, since the position gets negative. No big deal.}
  27. {• The code for the marble should be separated from the main program, to make the code easier}
  28. {to follow.}
  29. {I'll fix those things when I find time and inspiration for it - but you are welcome to do it if}
  30. {you want!}
  31.  
  32. program OffscreenToysSAT;
  33.     uses
  34. {$ifc UNDEFINED THINK_PASCAL}
  35.         Types, QuickDraw, Events, Windows, Dialogs,
  36.         Fonts, DiskInit, PackIntf,
  37.         TextEdit, Traps, Desk, Memory, SegLoad, Scrap,
  38.         ToolUtils, OSEvents, OSUtils, Menus, Resources,
  39.         StandardFile, GestaltEqu, Files, Errors,
  40. {$endc}
  41.         SAT_OT; {Customized version}
  42.  
  43. { --- PART 1: Variables and constants: -----------------------------------------}
  44.  
  45.     const
  46.         kAppleID = 128;
  47.         kFileID = 129;
  48.         kMBarHeight = 20;    { We assume 20 pixels menu bar for window sizing and dragging.}
  49.  
  50.         kWindID = 128;            { Window resource ID }
  51.         kAboutAlertID = 128;    { Alert resource ID }
  52.  
  53.         kSpriteNumber = 5;        { Number of moving objects }
  54.     var
  55.         gColorQDFlag: Boolean;        {True if 32-bit QD exists. If not, we run everything in b/w.}
  56.         gHasWNE: Boolean;        {True if we can use WaitNextEvent}
  57.         gSoundFlag: Boolean;        {True if we want sound.}
  58.         gFast: Boolean;
  59.  
  60.         gWhoa: Boolean;            {True when we want to quit}
  61.         gCollisionFlag: Boolean;    {Collisions or not?}
  62.  
  63. {Menu handles}
  64.         appleMenu, fileMenu: MenuHandle;
  65.  
  66. {The window we'll be using}
  67.         gWind: WindowPtr;
  68.  
  69. {Our two offscreens:}
  70. {offScreen, backScreen: GrafPtr;}
  71.  
  72. {A cicn loded as a face}
  73.         gCicn: FacePtr;
  74.         kgck: Handle;
  75.  
  76. {Sprite information. In real games, I prefer making a linked list of records, like I do in}
  77. {SAT, and a lot more information for each, but here we want it *simple*.}
  78. {- position: The positions in local coordinates for the window}
  79. {- fixedPos: 16 times position, which gives us fixed-point numbers}
  80. {- speed: Speed vectors that is added to fixedPos for every frame}
  81. {- r: Rectangles used in drawing, for remembering what part of the screen to update}
  82. {position, fixedPos: array[1..kSpriteNumber] of Point;}
  83. {speed: array[1..kSpriteNumber] of Point;}
  84. {r: array[1..kSpriteNumber] of Rect;}
  85.  
  86. {A longint that shows how big the "bowl" is (assumes circular, not oval, bowl!):}
  87.  
  88.         gBowlSize: Longint;
  89.  
  90. { --- PART 2: Various general, reuseable routines, mostly glue: ---------------------}
  91.  
  92. {BailOut: Emergency exit. We go here on most errors. Real programs report what the}
  93. {problem is. You may wish to put a breakpoint in BailOut when debugging.}
  94.  
  95.     procedure BailOut;
  96.     begin
  97.         SysBeep(1); {Minimal error message. Use alert in real programs.}
  98.         halt;
  99.     end;
  100.  
  101. {Several functions deleted since SAT handles what they do.}
  102.  
  103. { --- PART 3: Application specific routines: ---------------------------------}
  104.  
  105. {mouse clicks, keydowns, background tasks and update events: This is where all}
  106. {the action is. :-) I include some empty procedures for you to fill in if you want to}
  107. {use this demo as application shell.}
  108.  
  109. {Mouse click in window content}
  110.  
  111.     procedure DoMouse (where: Point; modifiers: Longint);
  112.     begin
  113.     end;
  114.  
  115. {Keydown.}
  116.  
  117.     procedure DoKey (theKey: Char; modifiers: Longint);
  118.     begin
  119.     end;
  120.  
  121.     const
  122.         kWallBounce = 7;                            {1/10-ths of speed kept after wallbounce}
  123.         kBallDiameterSquared = 32 * 32;            {Diameter 32, squared}
  124.  
  125.  
  126. {A rather boring subroutine that moves the sprites i and j away from each other.}
  127. {I almost didn't want to put this in, making the demo unnecessarily big, but I wanted}
  128. {decent collisions. If anyone can suggest a better (simpler) collision handling for circular}
  129. {objects, I'd be happy to put it in.}
  130. {I use a line drawing algorithm for it. I'm sure there are better ways. This is of questionable}
  131. {value as reuseable code - depends on your application.}
  132.     procedure Separate (i, j: SpritePtr);
  133.         var
  134.             initVector, nowVector: Point;
  135.             absH, absV: integer;
  136.             moveH, moveV: integer;
  137.             frac: integer;
  138. {Normal signum function (which I don't think is in the libs)}
  139.         function Sgn (x: integer): integer;
  140.         begin
  141.             if x > 0 then
  142.                 Sgn := 1
  143.             else if x < 0 then
  144.                 Sgn := -1
  145.             else
  146.                 Sgn := 0;
  147.         end;
  148.  
  149.     begin {Separate}
  150.         frac := 0;
  151.         initVector.h := i^.position.h - j^.position.h;
  152.         initVector.v := i^.position.v - j^.position.v;
  153.         absH := abs(initVector.h);
  154.         absV := abs(initVector.v);
  155.         moveH := Sgn(initVector.h);
  156.         moveV := Sgn(initVector.v);
  157.         if moveH = 0 then
  158.             if moveV = 0 then
  159.                 moveV := 1;
  160.         repeat
  161.             if absH > absV then
  162.                 begin
  163.                     i^.position.h := i^.position.h + moveH;
  164.                     j^.position.h := j^.position.h - moveH;
  165.                     frac := frac + absV;
  166.                     if frac > absH then
  167.                         begin
  168.                             i^.position.v := i^.position.v + moveV;
  169.                             j^.position.v := j^.position.v - moveV;
  170.                             frac := frac - absH;
  171.                         end
  172.                 end
  173.             else
  174.                 begin
  175.                     i^.position.v := i^.position.v + moveV;
  176.                     j^.position.v := j^.position.v - moveV;
  177.                     frac := frac + absH;
  178.                     if frac > absV then
  179.                         begin
  180.                             i^.position.h := i^.position.h + moveH;
  181.                             j^.position.h := j^.position.h - moveH;
  182.                             frac := frac - absV;
  183.                         end
  184.                 end;
  185.             nowVector.h := i^.position.h - j^.position.h;
  186.             nowVector.v := i^.position.v - j^.position.v;
  187.         until longint(nowVector.h) * nowVector.h + longint(nowVector.v) * nowVector.v > kBallDiameterSquared;
  188.         i^.fixedPos.h := BSL(i^.position.h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
  189.         i^.fixedPos.v := BSL(i^.position.v, 4);
  190.  
  191.         j^.fixedPos.h := BSL(j^.position.h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
  192.         j^.fixedPos.v := BSL(j^.position.v, 4);
  193.  
  194.     end;{Separate}
  195.  
  196. {Split a vector (v1) into one component parallell to another vector (direction) and one}
  197. {orthogonal to it.}
  198.     procedure SplitVector (v1, direction: Point; var parallell, normal: Point);
  199.         var
  200.             l2, v1pr: Longint;
  201.     begin
  202. {parallell := direction * (v1 DOT direction) /|direction|**2}
  203. {normal := v1 - parallell}
  204.  
  205.         l2 := direction.h * direction.h + direction.v * direction.v; {Squared length of "direction"}
  206.         v1pr := v1.h * direction.h + v1.v * direction.v; {Scalar product}
  207.  
  208.         parallell.h := direction.h * v1pr div l2;
  209.         parallell.v := direction.v * v1pr div l2;
  210.         normal.h := v1.h - parallell.h;
  211.         normal.v := v1.v - parallell.v;
  212.     end; {SplitVector}
  213.  
  214.     procedure HandleMarble (i: SpritePtr);
  215.         var
  216.             vector: Point;
  217.     begin
  218. {Modify fixed-point position by speed}
  219.         i^.fixedPos.h := i^.fixedPos.h + i^.speed.h;
  220.         i^.fixedPos.v := i^.fixedPos.v + i^.speed.v;
  221.  
  222. {Make position by shifting away the 4 binary "decimals"}
  223.         if i^.fixedPos.h >= 0 then
  224.             i^.position.h := BSR(i^.fixedPos.h, 4)
  225.         else
  226.             i^.position.h := BitOr(BSR(i^.fixedPos.h, 4), $f000);
  227.         if i^.fixedPos.v >= 0 then
  228.             i^.position.v := BSR(i^.fixedPos.v, 4)
  229.         else
  230.             i^.position.v := BitOr(BSR(i^.fixedPos.v, 4), $f000);
  231.  
  232. {Outside the allowed area?}
  233.         if i^.position.h < 0 then
  234.             begin
  235.                 i^.speed.h := abs(i^.speed.h) * kWallBounce div 10 + 1;
  236. {     i^.position.h := 0;}
  237.             end;
  238.         if i^.position.v < 0 then
  239.             begin
  240.                 i^.speed.v := abs(i^.speed.v) * kWallBounce div 10 + 1;
  241. { i^.position.v := 0; }
  242.             end;
  243.         if i^.position.h + gCicn^.iconMask.bounds.right > gSAT.offScreen^.portRect.right then
  244.             begin
  245.                 i^.speed.h := -abs(i^.speed.h) * kWallBounce div 10 - 1;
  246. { i^.position.h := gSAT.offScreen^.portRect.right - gCicn^.iconMask.bounds.right; }
  247.             end;
  248.         if i^.position.v + gCicn^.iconMask.bounds.bottom > gSAT.offScreen^.portRect.bottom then
  249.             begin
  250.                 i^.speed.v := -abs(i^.speed.v) * kWallBounce div 10 - 1;
  251. { i^.position.v := gSAT.offScreen^.portRect.bottom - gCicn^.iconMask.bounds.bottom; }
  252.             end;
  253.  
  254. {Are we in the bowl? If we are, accelerate towards the center.}
  255.         vector.h := i^.position.h + 16 - BSR(gSAT.offScreen^.portRect.right, 1);
  256.         vector.v := i^.position.v + 16 - BSR(gSAT.offScreen^.portRect.bottom, 1);
  257.         if (vector.h * vector.h + vector.v * vector.v) < gBowlSize then
  258.             begin
  259.                 i^.speed.h := i^.speed.h - vector.h div 2;
  260.                 i^.speed.v := i^.speed.v - vector.v div 2;
  261.             end;
  262.     end; {position/speed loop}
  263.  
  264.     procedure HitMarble (i, j: SpritePtr);
  265.         var
  266.             vector: Point;
  267.             squaredLength: Longint;
  268.             p1, p2, n1, n2, tmpSpeed: Point;
  269.     begin
  270.         if not gCollisionFlag then
  271.             exit(HitMarble);
  272.  
  273. {Find the vector between them}
  274.         vector.h := i^.position.h - j^.position.h;
  275.         vector.v := i^.position.v - j^.position.v;
  276.         squaredLength := longint(vector.h) * vector.h + longint(vector.v) * vector.v;
  277. {If it is shorter than the diameter of a ball…}
  278.         if squaredLength < kBallDiameterSquared then
  279.             begin
  280. {Move them away from each other}
  281.                 Separate(i, j);
  282.  
  283. {if false then}
  284.                 begin
  285. {Swap the speed components that are parallell to "vector" (this allows for "touches", very}
  286. {nice and realistic bounces)}
  287.                     SplitVector(i^.speed, vector, p1, n1);
  288.                     SplitVector(j^.speed, vector, p2, n2);
  289.  
  290.                     j^.speed.h := p1.h + n2.h;
  291.                     j^.speed.v := p1.v + n2.v;
  292.  
  293.                     i^.speed.h := p2.h + n1.h;
  294.                     i^.speed.v := p2.v + n1.v;
  295.                 end;
  296.  
  297. {Old Offscreen Toys just swapped the speed, as commented out below. This is not as realistic.}
  298. {tmpSpeed := i^.speed;}
  299. {i^.speed := j^.speed;}
  300. {j^.speed := tmpSpeed;}
  301.  
  302. {Play a sound. SAT is good at playing sounds!}
  303.                 if gSoundFlag then
  304.                     SATSoundPlay(kgck, 1, false);
  305.  
  306.             end;
  307.     end; {collision loop}
  308.  
  309.     procedure SetupMarble (i: SpritePtr);
  310.     begin
  311.         i^.fixedPos.h := BSL(i^.position.h, 4);
  312.         i^.fixedPos.v := BSL(i^.position.v, 4);
  313.         i^.speed.h := Random mod 32;
  314.         i^.speed.v := Random mod 32;
  315.         i^.task := @HandleMarble;
  316.         i^.hitTask := @HitMarble;
  317.         i^.face := gCicn;
  318.         SetRect(i^.hotRect, 0, 0, 32, 32);
  319.     end;
  320.  
  321. {A static variable only used in DrawBackground}
  322.     var
  323.         thePat: PixPatHandle;
  324.  
  325.     procedure DrawBackground;
  326.         const
  327.             patID = 128;
  328.         var
  329.             r: Rect;
  330.             mycolorFlag: Boolean;
  331.  
  332. {A little routine for setting the forecolor with a single line.}
  333.         procedure OTForeColor (red, green, blue: integer);
  334.             var
  335.                 theColor: RGBColor;
  336.         begin
  337.             theColor.red := red;
  338.             theColor.green := green;
  339.             theColor.blue := blue;
  340.             RGBForeColor(theColor);
  341.         end;
  342.  
  343.         var
  344.             savePort: GrafPtr;
  345.             saveGD: GDHandle;
  346.     begin {DrawBackground}
  347.         SATGetPort(savePort, saveGD);
  348.  
  349.         SATSetPortBackScreen;
  350.  
  351. {Do some drawing in backScreen. First, we paint a pattern (using a 'ppat' resource):}
  352.  
  353. {For drawing the background, let's make a local flag that tells us if we shold draw}
  354. {b/w patterns or color ones.}
  355.         if gColorQDFlag then
  356.             mycolorFlag := gSAT.initDepth > 1
  357.         else
  358.             mycolorFlag := false;
  359.  
  360.         if mycolorFlag then
  361.             begin
  362.                 if thePat = nil then
  363.                     thePat := GetPixPat(patID);
  364.                 PenPixPat(thePat)
  365.             end
  366.         else
  367.             begin
  368.                 if thePat = nil then
  369.                     thePat := PixPatHandle(GetResource('ppat', patID));
  370.                 PenPat(thePat^^.pat1Data);
  371.             end;
  372.         PaintRect(gSAT.backScreen^.portRect);
  373.         PenNormal;
  374.  
  375. {Then we draw some circles.}
  376.  
  377.         r := gSAT.backScreen^.portRect;
  378.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  379.         gBowlSize := longint(r.right - r.left) * (r.right - r.left) div 4;        {Tells how big the "bowl" is!}
  380.         if mycolorFlag then
  381.             begin
  382.                 OTForeColor(-10000, -10000, -10000);
  383.                 PaintOval(r);
  384.             end
  385.         else
  386. {$IFC UNDEFINED THINK_PASCAL}
  387.             FillOval(r, qd.ltGray);
  388. {$ELSEC}
  389.             FillOval(r, ltGray);
  390. {$ENDC}
  391.  
  392.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  393.         if mycolorFlag then
  394.             begin
  395.                 OTForeColor(-25000, -25000, -25000);
  396.                 PaintOval(r);
  397.             end
  398.         else
  399. {$IFC UNDEFINED THINK_PASCAL}
  400.             FillOval(r, qd.gray);
  401. {$ELSEC}
  402.             FillOval(r, gray);
  403. {$ENDC}
  404.  
  405.         InsetRect(r, (r.right - r.left) div 6, (r.bottom - r.top) div 6);
  406.         if mycolorFlag then
  407.             begin
  408.                 OTForeColor(20000, 20000, 20000);
  409.                 PaintOval(r);
  410.             end
  411.         else
  412. {$IFC UNDEFINED THINK_PASCAL}
  413.             FillOval(r, qd.dkGray);
  414. {$ELSEC}
  415.             FillOval(r, dkGray);
  416. {$ENDC}
  417.  
  418.         InsetRect(r, (r.right - r.left) div 5, (r.bottom - r.top) div 5);
  419.         if gSAT.colorFlag then
  420.                 OTForeColor(0, 0, 0);
  421.         PaintOval(r);
  422. {         InsetRect(r, (r.right - r.left) div 5, (r.bottom - r.top) div 5);}
  423. {        if mycolorFlag then}
  424. {            begin}
  425. {                OTForeColor(0, 0, 0);}
  426. {                PaintOval(r);}
  427. {            end}
  428. {        else}
  429. {            FillOval(r, black); }
  430.  
  431.         SATSetPort(savePort, saveGD);
  432.     end; {DrawBackground}
  433.  
  434. {DoBackground: repeating tasks - this is called repeatedly, after every event we get.}
  435.  
  436. {Note: If you are making a really Mac-friendly program, this is where you should drive}
  437. {the animation. However, it is hard to get high framerate then, since other programs}
  438. {(the Finder included) will process events which will make it less smooth.}
  439.  
  440.     procedure DoBackground;
  441.         var
  442.             tmpRect: Rect;
  443.             i, j: integer;
  444.             vector: Point;
  445.             saveGD: GDHandle;
  446.             savePort: GrafPtr;
  447.             tmpSpeed: Point;
  448.     begin {DoBackground}
  449.  
  450.         SATRun(gFast); {Eller konfigurerbart?}
  451.  
  452.     end; {DoBackground}
  453.  
  454.  
  455. {DoUpdate: handle update events, in this case by copying offScreen to the screen (gWind).}
  456.  
  457. {Note to beginners: A program without update events processing is not a real Mac program!}
  458. {All drawing you do must reach the update event handler in some way, or it might be lost,}
  459. {or worse, partially erased, which is really ugly.}
  460.  
  461.     procedure DoUpdate;
  462.         var
  463.             saveGD: GDHandle;
  464.             savePort: GrafPtr;
  465.     begin
  466.         if SATDepthChangeTest then
  467.             DrawBackground;
  468.         BeginUpdate(gWind);
  469.         SATRedraw;
  470.         EndUpdate(gWind);
  471.     end;
  472.  
  473. {DoAppleMenu and DoFileMenu: handle menu selections}
  474.  
  475.     procedure DoAppleMenu (item: integer);
  476.         var
  477.             str: Str255;
  478.             h: Handle;
  479.             saveGD: GDHandle;
  480.             savePort: GrafPtr;
  481.             ignore: integer;
  482.     begin
  483.         if item = 1 then
  484.             begin
  485.                 if Alert(kAboutAlertID, nil) = 1 then
  486.                     ; {Ignore result}
  487.             end
  488.         else
  489. {Apple menu other than "About": Code from TransSkel}
  490.             begin
  491.                 SATGetPort(savePort, saveGD); {I guess GetPort would be ok}
  492.                 GetItem(appleMenu, item, str);
  493.                 SetResLoad(false);
  494.                 h := GetNamedResource('DRVR', str);
  495.                 SetResLoad(true);
  496.                 if h <> nil then
  497.                     begin
  498.                         ResrvMem(SizeResource(h) + $1000);
  499.                         ignore := OpenDeskAcc(str);
  500.                     end;
  501.                 SATSetPort(savePort, saveGD);
  502.             end;
  503.     end; {DoAppleMenu}
  504.  
  505.     procedure DoFileMenu (item: integer);
  506.         var
  507.             start, finish, frames: Longint;
  508.             fpsStr: Str255;
  509.     begin
  510.         case item of
  511.             1:
  512. {Run animation without event processing until the user clicks the mouse}
  513. {Note: This runs the animation at maximum speed. In real programs, we}
  514. {must limit the speed with the system clock, e.g. inspect TickCount.}
  515.                 begin
  516.                     start := TickCount;
  517.                     frames := 0;
  518.                     while not Button do
  519.                         begin
  520.                             DoBackground;
  521.                             frames := frames + 1;
  522.                         end;
  523.                     finish := TickCount;
  524.                     NumToString(frames * 60 div (finish - start), fpsStr);
  525.                     ReportStr(stringof(fpsStr, ' frames/second'));
  526. {ParamText(fpsStr, ' frames/second', '', '');}
  527. {if Alert(129, nil) = 1 then}
  528.                 end;
  529.             2: 
  530.                 begin
  531.                     gCollisionFlag := not gCollisionFlag;
  532.                     CheckItem(fileMenu, 2, gCollisionFlag);
  533.                 end;
  534.             3: 
  535.                 begin
  536.                     gFast := not gFast;
  537.                     CheckItem(fileMenu, 3, gFast);
  538.                 end;
  539.             4: 
  540.                 begin
  541.                     gSoundFlag := not gSoundFlag;
  542.                     CheckItem(fileMenu, 4, gSoundFlag);
  543.                 end;
  544. {Set the flag that tells the program to quit.}
  545.             6: 
  546.                 gWhoa := true;
  547.         end; {case}
  548.     end; {DoFileMenu}
  549.  
  550. { --- PART 4: Event processing: -----------------------------------------}
  551.  
  552. {MenuSelection: Menu selection by mouse or command-key:}
  553.  
  554.     procedure MenuSelection (whatSelection: longInt);
  555.     begin
  556.         case HiWord(whatSelection) of
  557.             kAppleID: 
  558.                 DoAppleMenu(LoWord(whatSelection));
  559.             kFileID: 
  560.                 DoFileMenu(LoWord(whatSelection));
  561.         end; {case}
  562.         HiLiteMenu(0);
  563.     end;
  564.  
  565. {MainLoop: get and process events. This is the boring standard part of all programs. I prefer}
  566. {using TransSkel to get rid of it. I don't here since I want this code to be stand-alone.}
  567.  
  568.     procedure MainLoop;
  569.         const
  570.             kSleep = 5; {Real programs may modify the sleep time depending on whether or not they are in the front}
  571.         var
  572.             hasEvent: Boolean;
  573.             theEvent: EventRecord;
  574.             theKey: Char;
  575.             whatSelection: Longint;
  576.             whichPart: integer;
  577.             whichWindow: WindowPtr;
  578.             r: rect;
  579.             p: Point;
  580.     begin
  581. {Get the next event. Use WaitNextEvent if possible.}
  582.         if gHasWNE then
  583.             hasEvent := WaitNextEvent(everyEvent, theEvent, kSleep, nil)
  584.         else
  585.             begin
  586.                 SystemTask;
  587.                 hasEvent := GetNextEvent(everyEvent, theEvent);
  588.             end;
  589.  
  590. {OK, so what happened then?}
  591.         if hasEvent then
  592.             case theEvent.what of
  593.                 mouseDown: 
  594.                     begin
  595.                         whichPart := FindWindow(theEvent.where, whichWindow);
  596.                         case whichPart of
  597.                             inMenuBar: 
  598.                                 begin
  599.                                     whatSelection := MenuSelect(theEvent.where);
  600.                                     MenuSelection(whatSelection);
  601.                                 end;
  602.                             inSysWindow: 
  603.                                 SystemClick(theEvent, whichWindow);
  604.                             inGoAway: 
  605.                                 if (TrackGoAway(whichWindow, theEvent.where)) then
  606.                                     gWhoa := true;
  607.                             inDrag: 
  608.                                 begin
  609.                                     if (whichWindow <> FrontWindow) and (BitAnd(theEvent.modifiers, cmdKey) = 0) then
  610.                                         SelectWindow(whichWindow);
  611.                                     r := gSAT.bounds;            {How big is the screen?}
  612. {Was: screenBits.bounds;{How big is the screen? (Note: Don't use screenBits for other things: it isn't a valid BitMap any more!)}
  613.                                     r.top := r.top + kMBarHeight;        { Skip down past menu bar    }
  614.  
  615.                                     InsetRect(r, 4, 4);
  616.  
  617. { LIMIT THE DRAGGING so no part of the window can get outside the screen! Cut down on r}
  618. {depending on where the click is? This is necessary if we use "fast mode", that is if we do}
  619. {SATRun(true) or any other operation with custom blitters.}
  620.                                     SetPort(whichWindow);
  621.                                     p := theEvent.where;
  622.                                     GlobalToLocal(p);
  623.                                     r.bottom := r.bottom - (whichWindow^.portRect.bottom - p.v);
  624.                                     r.left := r.left + p.h;
  625.                                     r.right := r.right - (whichWindow^.portRect.right - p.h);
  626.  
  627.                                     DragWindow(whichWindow, theEvent.where, r);
  628.  
  629.                                     if whichWindow = gSAT.wind then
  630.                                         begin
  631. { *** Adjust gSAT.ox and gSAT.oy. *** }
  632. {gSAT.ox and gSAT.oy are internal SAT variables that tell the offset from the upper left}
  633. {corner of the animation area to the upper left corner of the screen. You normally have no}
  634. {need to use them at all.}
  635.                                             p := gSAT.wind^.portRect.topLeft;
  636.                                             LocalToGlobal(p);
  637.  
  638. {Align the offscreens to the screen AT LEAST byte-wise; for best performance, you should}
  639. {longword-align!}
  640.                                             if gSAT.initDepth = 1 then
  641.                                                 if BitAnd(p.h, 15) <> 0 then
  642.                                                     begin
  643.                                                         p.h := p.h - BitAnd(p.h, 15);
  644.                                                         MoveWindow(gSAT.wind, p.h, p.v, true)
  645.                                                     end
  646.                                                 else if gSAT.initDepth = 4 then
  647.                                                     if BitAnd(p.h, 1) <> 0 then
  648.                                                         begin
  649.                                                             p.h := p.h - 1;
  650.                                                             MoveWindow(gSAT.wind, p.h, p.v, true)
  651.                                                         end;
  652.  
  653. {Now we know where the window ends up, and can adjust gSAT.h and gSAT.v}
  654.                                             gSAT.ox := -p.h;
  655.                                             gSAT.oy := -p.v;
  656.                                         end;
  657.  
  658.                                 end;
  659.                             inGrow: 
  660.                                 ;  {Ignored - we don't resize}
  661.                             inContent: 
  662.                                 if (whichWindow <> FrontWindow) then
  663.                                     SelectWindow(whichWindow)
  664.                                 else
  665.                                     DoMouse(theEvent.where, theEvent.modifiers); {Go to application-specific mouse down handling}
  666.                         end; {case whichPart}
  667.                     end; {mouseDown}
  668.                 keyDown, autoKey: 
  669.                     begin
  670.                         theKey := char(BitAnd(theEvent.message, charCodeMask));
  671.                         if (BitAnd(theEvent.modifiers, cmdKey) <> 0) then
  672.                             MenuSelection(MenuKey(theKey))
  673.                         else
  674.                             DoKey(theKey, theEvent.modifiers);
  675.                     end;
  676.                 updateEvt:
  677. {There's only one window to bother with here, but let's make sure that's the one the Mac wants to update.}
  678.                     if WindowPtr(theEvent.message) = gWind then
  679.                         DoUpdate;
  680. {Handle disk inserts like TransSkel.}
  681.                 diskEvt: 
  682.                     if (HiWord(theEvent.message) <> noErr) then
  683.                         begin
  684.                             DILoad;
  685.                             if DIBadMount(Point($00400040), theEvent.message) = 0 then
  686.                                 ;
  687.                             DIUnload;
  688.                         end; {diskEvt}
  689.                 otherwise {Other events are ignored}
  690.             end; {case}
  691.  
  692.         DoBackground;
  693.     end;
  694.  
  695. { --- PART 5: Initializations: -----------------------------------------}
  696.  
  697. {OTInit: Initialize global flags, menus and window}
  698.  
  699.     procedure OTInit;
  700.         const
  701. {Trap numbers}
  702.             _WaitNextEvent = $A860;
  703.             _GetCIcon = $AA1E; {E.g. any Color QuickDraw routine}
  704.             k32bQD = $AB1D;
  705.             _SndPlay = $A805;
  706.     begin
  707. {In case this isn't Think Pascal we have to make the standard inits ourselves.}
  708. {$IFC UNDEFINED THINK_PASCAL}
  709.         InitGraf(@qd.thePort);
  710.         InitFonts;
  711.         InitWindows;
  712.         InitMenus;
  713.         TEInit;
  714.         InitDialogs(nil);
  715. {InitCursor;}
  716.         MaxApplZone;
  717. {$ENDC}
  718.  
  719.         gHasWNE := SATTrapAvailable(_WaitNextEvent);
  720.         gColorQDFlag := SATTrapAvailable(k32bQD) and SATTrapAvailable(_GetCIcon); {???}
  721.         gWhoa := false;
  722.         gCollisionFlag := false;
  723.  
  724. {gSoundFlag := TrapAvailable(_SndPlay); – Let SAT decide if we CAN or not!}
  725.         gSoundFlag := true;
  726.  
  727. {What more should I check for? Check with Gestalt instead?}
  728.  
  729. {$IFC UNDEFINED THINK_PASCAL}
  730.         qd.randSeed := TickCount;            {Seed the random number generator - TickCount is good enough.}
  731. {$ELSEC}
  732.         randSeed := TickCount;            {Seed the random number generator - TickCount is good enough.}
  733. {$ENDC}
  734.  
  735. {Get the window, a color window if we are going to use color.}
  736.         if gColorQDFlag then
  737.             gWind := GetNewCWindow(kWindId, nil, WindowPtr(-1))
  738.         else
  739.             gWind := GetNewWindow(kWindId, nil, WindowPtr(-1));
  740.  
  741. {Some menus. We could read these from resources.}
  742.         appleMenu := NewMenu(kAppleID, stringof(char($14)));
  743.         AppendMenu(appleMenu, 'About Offscreen Toys SAT…;(-');
  744.         AddResMenu(appleMenu, 'DRVR');
  745.         InsertMenu(appleMenu, 0);            { put apple menu at end of menu bar }
  746.         fileMenu := NewMenu(kFileID, 'File');
  747.         AppendMenu(fileMenu, 'Try max speed;Collisions;Use SAT blitters;Use sound;(-;Quit/Q');
  748.         InsertMenu(fileMenu, 0);            { put file menu at end of menu bar }
  749.         DrawMenuBar;
  750.         CheckItem(fileMenu, 4, gSoundFlag);
  751.     end;
  752.  
  753. {OTOffscreensInit: Initialize offscreen grafports (worlds) and draw in them.}
  754.  
  755.     procedure OTOffscreensInit;
  756.         var
  757.             saveGD: GDHandle;
  758.             savePort: GrafPtr;
  759.             r: Rect;
  760.             i: integer;
  761.             sp: SpritePtr;
  762.     begin {OTOffscreensInit}
  763.         SATGetPort(savePort, saveGD);
  764.  
  765.         SATConfigure(false, kNoSort, kForwardOneCollision, 32); {Only call *one* hitTask, not both.}
  766.         SATCustomInit(0, 0, gWind^.portRect, gWind, nil, false, false, false, true, false);
  767.  
  768.         DrawBackground;
  769.  
  770. {Done drawing!}
  771. {For your own hacks, consider using a PICT resource and use GetPicture and DrawPicture to draw the}
  772. {background. Note that you'll need both a color and a b/w picture if you want it to look good in b/w.}
  773.  
  774.         SATSetPortOffScreen;
  775.         CopyBits(gSAT.backScreen^.portBits, gSAT.offScreen^.portBits, gSAT.backScreen^.portRect, gSAT.backScreen^.portRect, srcCopy, nil);
  776.  
  777.         SATSetPort(savePort, saveGD);
  778.  
  779. {Get the cicn resource}
  780. {Note: You can, of course, use several cicns and switch between.}
  781.         gCicn := SATGetFace(128);
  782.         kgck := SATGetNamedSound('Kgck');
  783.  
  784. {Initialize the sprites:}
  785.  
  786.         for i := 1 to kSpriteNumber do
  787.             sp := SATNewSprite(1, Rand(gSAT.offScreen^.portRect.right - 32), (i - 1) * (gSAT.offScreen^.portRect.bottom - 32) div 5 + Rand((gSAT.offScreen^.portRect.bottom - 32) div 5), @SetupMarble);
  788.  
  789.         if SATSoundInitChannels(2) < 2 then
  790.             SysBeep(1);
  791.     end;
  792.  
  793. { --- MAIN PROGRAM BODY: -----------------------------------------}
  794.  
  795. begin
  796.     OTInit;                    {General initializations}
  797.     OTOffscreensInit;        {Set up the offscreen grafports}
  798.     InitCursor;                {Set the cursor to arrow in case it isn't.}
  799.     SATSetPortScreen;    {The front window is a good port to use.}
  800.  
  801. {Run until quit or click in the close box.}
  802.     repeat
  803.         SATSetPortScreen;    {The front window is a good port to use.}
  804.         MainLoop;
  805.     until gWhoa;
  806.  
  807. {No cleanup is necessary here.}
  808. {We could DisposeGWorld, but that isn't necessary when we are quitting.}
  809.     SATSoundShutup;
  810. end.